//************R5**************

#include "R5.h"

dcb device1 = {CLOSED};
void interrupt (*currentIntHand)() = NULL;

int high_called = 0;
int high_dispatched = 0;

//Definitions
int com_open(int *eflag_p, int baud_rate){
	int baud_rate_divisor;
	int mask;
	
	if(*eflag_p == NULL){
		printf("Invalid even flag pointer.");
		return -101;
	}
	else{
		if(
			baud_rate != 110 &&
			baud_rate != 150 &&
			baud_rate != 300 &&
			baud_rate != 600 &&
			baud_rate != 1200 &&
			baud_rate != 2400 &&
			baud_rate != 4800 &&
			baud_rate != 9600 &&
			baud_rate != 19200
		){
			printf("Invalid baud rate divisor.");
			return -102;
		}
		else{
			if(device1.flag == OPEN){ // check to see if device is not open
				printf("Port is already open.");
				return -103;
			}
			else{
				/*Initialize the DCB. Sets the device to open, 
				saves a copy of the event flag pointer, 
				and sets the intital device status to idle.
				Also initializes the ring buffer parameters */
				device1.flag = OPEN;
				device1.event_flag = eflag_p;
				device1.status_code = IDLE;
				device1.ring_buffer_in = 0;
				device1.ring_buffer_out = 0;
				device1. ring_buffer_count = 0;
				
				/* Saves current interrupt handler*/
				currentIntHand = getvect(COM1_INT_ID);
				
				/*Install new handler*/
				setvect(COM1_INT_ID, high);
				// setvect(0x30, &high);
				
				/*Compute the baud rate divisor*/
				baud_rate_divisor = 115200 / (long) baud_rate;
				
				/* Store the value 0x80 in the Line Control Register. This
				 * allows the first two port addresses to access the Baud Rate
				 * Divisor register.
				 */
				outportb(COM1_LC, 0x80);
				
				/* Store the high order and low order bytes of the baud rate
				 * divisor into the MSB and LSB registers, respectively.
				 */
				outportb(COM1_BRD_LSB, baud_rate_divisor & 0xFF);
				outportb(COM1_BRD_MSB, (baud_rate_divisor >> 8) & 0xFF);
				
				/* Store the value 0x03 in the Line Control Register. This sets
				 * the line characteristics to 8 data bits, 1 stop bit, and no
				 * parity. It also restores normal functioning of the first two
				 * ports.
				 */
				outportb(COM1_LC, 0x03);
				
				/* Enable the appropriate level in the PIC mask register.
				 */
				disable();
				mask = inportb(PIC_MASK);
				mask = mask & ~0x10; //& ~0x80;
				outportb(PIC_MASK, mask);
				enable();
				
				/* Enable overall serial port interrupts by storing the value
				 * 0x08 in the Modem Control register.
				 */
				outportb(COM1_MC, 0x08);
				
				/* Enable input ready interrupts only by storing the value 0x01
				 * in the Interrupt Enable register.
				 */
				outportb(COM1_INT_EN, 0x01);
			}
		}
	}
	return 0;
}


int com_close(void){
	int mask;
	/*Check to see that the port is already open*/
	if(device1.flag == OPEN){
		/*Close the DCB*/
		device1.flag = CLOSED;
		/*Disable the appropriate level in the PIC mask register*/
		disable();
		mask = inportb(PIC_MASK);
		mask = mask | 0x10; //| 0x80;
		outportb(PIC_MASK, mask);
		enable();
		/*Load 0x00 into the Modem Status and Interrupt Enable registers*/
		outportb(COM1_MS, 0x00);
		outportb(COM1_INT_EN, 0x00);
		/*Restore the original interrupt vector*/
		setvect(COM1_INT_ID, currentIntHand);
		return 0;
	}
	else{
		printf("Serial port not open");
		return -201;
	}
}

int com_read(char *buf_p, int *count_p){

	if(device1.flag != OPEN){
		printf("Port not open.");
		return -301;
	}
	else{
		if(device1.status_code != IDLE){
			printf("Device is busy.");
			return -304;
		}
		else{
			if(buf_p == NULL){
				printf("Invalid Buffer address.");
				return -302;
			}
			else{
				if(count_p == NULL || *count_p <= 0){
					printf("Invalid count address or count value.");
					return -303;
				}
				else{
					/*Save the address of the requestor's buffer*/
					device1.in_buff = buf_p;
					/*Save the address of the count*/
					device1.in_count = count_p;
					/*Set input done count to 0*/
					device1.in_done = 0;
					/*Clear the event flag*/
					*device1.event_flag = 0;
					/*Set status to reading*/
					device1.status_code = READING; 
					/*Disable interrupts*/
					disable();

					while(device1.ring_buffer_count != 0 && device1.in_done < *count_p){
						int index = device1.ring_buffer_out;
						
						buf_p[device1.in_done++] = device1.ring_buffer[index];
						device1.ring_buffer_out = (index +1) % BUFSIZ;
						device1.ring_buffer_count--;
						
						if(device1.ring_buffer[index] == '\r'){
							break;
						}
					}
					
					enable();
					
					if (device1.in_done >= *count_p || buf_p[device1.in_done] == '\r') {
						/*
						buf_p[device1.in_done] = '\0';
						//*/
						
						if (buf_p[device1.in_done - 1] == '\r') {
							buf_p[--device1.in_done] = '\0';
						} else {
							buf_p[device1.in_done] = '\0';
						}
						
						device1.status_code = IDLE;
						*device1.event_flag = 1;
						*count_p = device1.in_done;
					}
					
					return 0;
				}
			}
		}
	}
}

int com_write(char *buf_p, int *count_p){
	int mask;
	if(device1.flag != OPEN){
		printf("Serial port not open");
		return -401;
	}
	else{
		if(device1.status_code != IDLE){
			printf("Device is busy");
			return -404;
		}
		else{
			if(buf_p == NULL){
				printf("Invalid Buffer Address.");
				return -402;
			}	
			else{
				if(count_p == NULL || *count_p <= 0){
					printf("Invalid count address or count value.");
					return -403;
				}
				else if(device1.ring_buffer_count >= BUFSIZ){
					printf("Buffer is full.");
					return -405;
				}
				else{
					/*Save the address of the requestor's buffer*/
					device1.out_buff = buf_p;
					/*Save the count*/
					device1.out_count = count_p;
					/*Set the output done count to 0*/
					device1.out_done = 0;
					/*Set status to writing*/
					device1.status_code = WRITING;
					/*Clear the event flag*/
					*device1.event_flag = 0;
					/*Write the first character to the com port*/
					//disable();
					outportb(COM1_BASE, *device1.out_buff);
					/*Advance the output buffer*/
					// device1.out_buff = device1.out_buff + 1;
					/*Increment output done*/
					device1.out_done++;
					/*Enable the write interrupts*/
					//enable();
					disable();//added
					mask = inportb(COM1_INT_EN);
					mask = mask | 0x02;
					outportb(COM1_INT_EN, mask);
					enable(); //added
				}
			}
		
		}
	}
	return 0;
}

//Interrupt Handlers
void interrupt high(){
	unsigned char interrupt_source;
	
	high_called++;
	
	/* If the port is not open, clear the interrupt and return.
	 */
	if (device1.flag != OPEN) {
		outportb(PIC_CMD, PIC_CMD);
		return;
	}
	
	/* Read the Interrupt ID register to determine the exact cause of the
	 * interrupt. Bit 0 must be a 0 if the interrupt was actually caused by the
	 * serial port. In this case, bits 2 and 1 indicate the specific interrupt
	 * type as follows:
	 *
	 * Bit2  Bit1  InterruptType
	 * ----------------------------------
	 *    0     0  Modem Status Interrupt
	 *    0     1  Output Interrupt
	 *    1     0  Input Interrupt
	 *    1     1  Line Status Interrupt
	 */
	interrupt_source = inportb(COM1_INT_ID_REG);
	if (interrupt_source & 1) {
		outportb(PIC_CMD, PIC_CMD);
		return;
	}
	
	high_dispatched++;
	
	interrupt_source = (interrupt_source >> 1) & 3;
	
	/* Call the appropriate second-level handler.
	 */
	switch (interrupt_source) {
		case MODEM_STATUS:
		case LINE_STATUS:
			break;
		
		case OUTPUT:
			write_out();
			break;
		
		case INPUT:
			read_in();
			break;
	}
	
	/* Clear the interrupt by sending EOI to the PIC command register.
	 */
	outportb(PIC_CMD, PIC_CMD);
}

void read_in(){
	
	unsigned char in_char;
	in_char = inportb(COM1_BASE);
	if(device1.status_code != READING){
		if(device1.ring_buffer_count == BUFSIZ){
			in_char = NULL;
			return;
		}
		else{
			device1.ring_buffer[device1.ring_buffer_in] = in_char;
			device1.ring_buffer_count++;
			device1.ring_buffer_in = (device1.ring_buffer_in +1) % BUFSIZ;
		}
	}
	else{
		device1.in_buff[device1.in_done++] = in_char;
		if(in_char == '\r' || *device1.in_count == device1.in_done){
			/*
			device1.in_buff[device1.in_done] = '\0';
			//*/
			
			if (in_char == '\r') {
				device1.in_buff[--device1.in_done] = '\0';
			} else {
				device1.in_buff[device1.in_done] = '\0';
			}
			
			device1.status_code = IDLE;
			*device1.event_flag = 1;
			*device1.in_count = device1.in_done;
		}
	}
}

void write_out(){
	int mask;
	if(device1.status_code != WRITING){
		return;
	}
	else{
		/*
		if(device1.out_count != device1.out_done){ //fix logic
			device1.out_buff[device1.out_done++] =  out_char;
			return;
		}
		device1.status_code = IDLE;
		*device1.event_flag = 1;
		mask = inportb(COM1_INT_EN);
		mask = mask & ~0x02;
		outport(COM1_INT_EN, mask);
		*/
		// device1.out_buff[device1.out_done++] = out_char;
		outportb(COM1_BASE, device1.out_buff[device1.out_done++]);

		if (device1.out_done >= *device1.out_count) {
			device1.status_code = IDLE;
			*device1.event_flag = 1;
			mask = inportb(COM1_INT_EN);
			mask = mask & ~0x02;
			outport(COM1_INT_EN, mask);
		}
	}

}